
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>          
#include <arpa/inet.h>
#include <sys/fcntl.h>
#include <sys/epoll.h>
#include <netinet/tcp.h>      // TCP_NODELAY需要包含这个头文件。
#include "InetAddress.h"
void setnonblocking(int fd)
{
    int event = fcntl(fd, F_GETFL);
    if (event == 0)
    {
        return;
    }
    fcntl(fd, F_SETFL, event | O_NONBLOCK);
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        printf("usage: ./tcpepoll ip port\n");
        printf("example: ./tcpepoll 127.0.0.1 8080\n");
        return -1;
    }
    int listenFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenFd < 0)
    {
        // 创建失败
        perror("socket() failed");
        return -1;
    }

    int opt = 1;
    // 前两个是为了ip 和 port可以重复利用
    setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, &opt, static_cast<socklen_t>(sizeof opt)); // 必须的。
    setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, &opt, static_cast<socklen_t>(sizeof opt)); // 有用，但是，在Reactor中意义不大。
    setsockopt(listenFd, SOL_SOCKET, TCP_NODELAY, &opt, static_cast<socklen_t>(sizeof opt));  // 必须的。
    setsockopt(listenFd, SOL_SOCKET, SO_KEEPALIVE, &opt, static_cast<socklen_t>(sizeof opt)); // 可能有用，但是，建议自己做心跳。

    setnonblocking(listenFd);

    // struct sockaddr_in serveraddr;
    // // memset(&serveraddr, 0, sizeof(serveraddr));
    // serveraddr.sin_family = AF_INET;
    // serveraddr.sin_addr.s_addr = inet_addr(argv[1]); // 做了两件事 ，第一将字符串变为数值形式 ，另外一件事 大端转发
    // serveraddr.sin_port = htons(atoi(argv[2]));
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);
    InetAddress serveraddr(ip,port);

    // int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    if (bind(listenFd, serveraddr.addr(), sizeof(serveraddr)) < 0)
    {
        perror("bind() failed");
        close(listenFd);
        return -1;
    }

    // 第二个参数是tcp半连接的数量
    if (listen(listenFd, 128) < 0)
    {
        perror("listen() failed");
        close(listenFd);
        return -1;
    }

    // 尽管 size 参数在现代 Linux 内核中已经没有实际作用，但它仍然是一个必需的参数。通常情况下，传递 1 或更大的值是可以接受的。因此，您会看到代码中使用 epoll_create(1)，以符合函数的参数要求。
    int epfd = epoll_create(1);

    struct epoll_event ev;
    ev.data.fd = listenFd;
    // 监听读事件
    ev.events = EPOLLIN;

    epoll_ctl(epfd, EPOLL_CTL_ADD, listenFd, &ev);

    struct epoll_event evs[10]; // 这个数据是处理就绪的

    while (true)        // 事件循环。
    {
        int infds=epoll_wait(epfd,evs,10,-1);       // 等待监视的fd有事件发生。

        // 返回失败。
        if (infds < 0)
        {
            perror("epoll_wait() failed"); break;
        }

        // 超时。
        if (infds == 0)
        {
            printf("epoll_wait() timeout.\n"); continue;
        }

        // 如果infds>0，表示有事件发生的fd的数量。
        for (int ii=0;ii<infds;ii++)       // 遍历epoll返回的数组evs。
        {
            if (evs[ii].events & EPOLLRDHUP)                     // 对方已关闭，有些系统检测不到，可以使用EPOLLIN，recv()返回0。
            {
                printf("client(eventfd=%d) disconnected.\n",evs[ii].data.fd);
                close(evs[ii].data.fd);            // 关闭客户端的fd。
            }                                //  普通数据  带外数据
            else if (evs[ii].events & (EPOLLIN|EPOLLPRI))   // 接收缓冲区中有数据可以读。
            {
                if (evs[ii].data.fd==listenFd)   // 如果是listenFd有事件，表示有新的客户端连上来。
                {
                    ////////////////////////////////////////////////////////////////////////
                    struct sockaddr_in peeraddr;
                    socklen_t len = sizeof(peeraddr);
                    // InetAddress peeraddr;
                    // socklen_t len =  sizeof(peeraddr);
                    int clientfd = accept4(listenFd,(struct sockaddr*)&peeraddr,&len,SOCK_NONBLOCK);
                    InetAddress clientaddr(peeraddr);
                    printf ("accept client(fd=%d,ip=%s,port=%d) ok.\n",clientfd,clientaddr.ip(),clientaddr.port());

                    // 为新客户端连接准备读事件，并添加到epoll中。
                    ev.data.fd=clientfd;
                    ev.events=EPOLLIN|EPOLLET;           // 边缘触发。
                    epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
                    ////////////////////////////////////////////////////////////////////////
                }
                else                                        // 如果是客户端连接的fd有事件。
                {
                    ////////////////////////////////////////////////////////////////////////
                    char buffer[1024];
                    while (true)             // 由于使用非阻塞IO，一次读取buffer大小数据，直到全部的数据读取完毕。
                    {    
                        bzero(&buffer, sizeof(buffer));
                        ssize_t nread = read(evs[ii].data.fd, buffer, sizeof(buffer));
                        if (nread > 0)      // 成功的读取到了数据。
                        {
                            // 把接收到的报文内容原封不动的发回去。
                            printf("recv(eventfd=%d):%s\n",evs[ii].data.fd,buffer);
                            send(evs[ii].data.fd,buffer,strlen(buffer),0);
                        } 
                        else if (nread == -1 && errno == EINTR) // 读取数据的时候被信号中断，继续读取。
                        {  
                            continue;
                        } 
                        else if (nread == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) // 全部的数据已读取完毕。
                        {
                            break;
                        } 
                        else if (nread == 0)  // 客户端连接已断开。
                        {  
                            printf("client(eventfd=%d) disconnected.\n",evs[ii].data.fd);
                            close(evs[ii].data.fd);            // 关闭客户端的fd。
                            break;
                        }
                    }
                }
            }
            else if (evs[ii].events & EPOLLOUT)                  // 有数据需要写，暂时没有代码，以后再说。
            {
            }
            else                                                                   // 其它事件，都视为错误。
            {
                printf("client(eventfd=%d) error.\n",evs[ii].data.fd);
                close(evs[ii].data.fd);            // 关闭客户端的fd。
            }
        }
    }
    return 0;
}